import json
from collections import OrderedDict
from collections.abc import Iterable
from functools import update_wrapper
from weakref import WeakSet

from django.contrib.auth import REDIRECT_FIELD_NAME
from django.db.models.base import ModelBase
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import reverse_lazy, reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from .options import BaseAdmin


class AdminzSite:
    # 收集全部的 AdminzSite 实例
    all_adminz_sites = {}

    @classmethod
    def add_adminz_site(cls, adminz_site):
        if isinstance(adminz_site, AdminzSite):
            if adminz_site.name in cls.all_adminz_sites.keys():
                raise Exception("AdminzSite {0} has been registered.".format(adminz_site.name))
            cls.all_adminz_sites[adminz_site.name] = adminz_site

    def __init__(self, name="adminz"):
        self.name = name
        self._models = {}
        # self._registry = self._registered  # 为了兼容使用 autodiscover_modules 加载 adminz.py文件
        self._actions = {}
        self._global_actions = self._actions.copy()

        self.title = "Title"
        self.sub_title = "Subtitle"
        self.index_page_template = "adminz/index.html"
        self.login_page_template = "adminz/login.html"
        self.login_page_form = None

        self.frame_page_template = ""

        AdminzSite.add_adminz_site(self)

    from django.db.models.base import ModelBase

    def is_registered_model(self, model):
        if isinstance(model, ModelBase) and model in self._models.keys():
            return True
        return False

    def is_registered_action(self, action):
        if callable(action) and action in self._actions.keys():
            return True
        return False

    def register(self, model_or_iterable, admin_class=None, **options):
        admin_class = admin_class or BaseAdmin
        if isinstance(model_or_iterable, ModelBase):
            model_or_iterable = [model_or_iterable]
        for model in model_or_iterable:
            if isinstance(model, ModelBase):
                if self.is_registered_model(model):
                    raise Exception("Model already registered.")
                self._models[model] = admin_class(model, self)

    def register_action(self, action, info=None):
        if callable(action):
            if self.is_registered_action(action):
                raise Exception("Action already registered.")
            self._actions[action] = info
        else:
            raise Exception("Action is not callable.")

    def each_context(self, request, extra_context=None):
        """
        Return a dictionary of variables to put in the template context for
        *every* page in the admin site.

        For sites running on a subpath, use the SCRIPT_NAME value if site_url
        hasn't been customized.
        """
        app_and_model = self.get_app_and_model(request, extra_context)
        action_and_info = self.get_action_and_info(request, extra_context)

        context = {
            "title": self.title,
            "sub_title": self.sub_title,
            "has_permission": self.has_permission(request),
            "index_path": reverse("{0}:index".format(self.name)),
            "login_path": reverse("{0}:login".format(self.name)),
            "logout_path": reverse("{0}:logout".format(self.name)),
            "action_names": [info["action_name"] for action, info in self._actions.items()],
            "models": self._models,
            "app_label_then_model_info": app_and_model,
            "action_and_info": action_and_info
        }
        context.update(extra_context or {})
        return context

    def index(self, request, extra_context=None):
        """
        Display the main admin index page, which lists all of the installed
        apps that have been registered in this site.
        """

        context = {
            **self.each_context(request),
            "title": self.title,
            "subtitle": self.sub_title,
            **(extra_context or {})
        }

        return TemplateResponse(
            request, self.index_page_template, context
        )

    def get_app_and_model(self, request, extra_context=None):
        r = self._get_app_and_model(request, extra_context)
        app_labels = self.sort_app_labels(r.keys(), request, extra_context)
        ordered_r = OrderedDict()
        for app_label in app_labels:
            ordered_r[app_label] = self.sort_model_infos(r[app_label], request, extra_context)
        return ordered_r

    def _get_app_and_model(self, request, extra_context=None):
        models = self._models
        r = {}
        for model, model_admin in models.items():
            app_label = model._meta.app_label
            model_name = model._meta.model_name
            model_info = {
                "model": model,
                "name": model._meta.model_name,
                "object_name": model._meta.object_name,
                "perms": {},
                "change_list_url": reverse("adminz:%s_%s_changelist" % (app_label, model_name)),
                "add_url": None,
                "change_url": None
            }
            if app_label not in r.keys():
                r[app_label] = []
            r[app_label].append(model_info)
        return r

    def sort_app_labels(self, app_labels, request, extra_context=None):
        return app_labels

    def sort_model_infos(self, model_infos, request, extra_context=None):
        return model_infos

    def get_action_and_info(self, request, extra_context=None):
        r = self._get_action_and_info(request, extra_context)
        action_names = self.sort_action_names(r.keys(), request, extra_context)
        ordered_r = OrderedDict()
        for action_name in action_names:
            ordered_r[action_name] = r[action_name]
        return ordered_r

    def _get_action_and_info(self, request, extra_context=None):
        actions = self._actions
        r = {}
        for action, info in actions.items():
            action_name = (info or {}).get("action_name")
            info.update({
                "perms": {},
                "action_url": reverse("adminz:action_{0}".format(action_name))
            })
            r[action_name] = info
        return r

    def sort_action_names(self, action_names, request, extra_context=None):
        return action_names

    def authenticate(self, username, password):
        # 这里可以实现简单的用户名和密码验证逻辑
        # 例如与数据库中的用户信息进行比对
        return False

    def login(self, request, extra_context=None):

        index_path = reverse("{0}:index".format(self.name))

        context = {
            **self.each_context(request),
            "title": self.title,
            "subtitle": self.sub_title,
            "fullpath": request.get_full_path(),
            "username": request.user.get_username(),
            **(extra_context or {}),
        }

        if request.method == "GET" and self.has_permission(request):
            # Already logged-in, redirect to admin index
            index_path = reverse("adminz:index")
            return HttpResponseRedirect(index_path)

        from .forms import AdminzAuthenticationForm
        from django.contrib.auth.views import LoginView

        # 参考 Admin ，追溯到 AuthenticationForm 和 Admin Login Html
        # 自定义的页面至少需要包括 name 为 username 和 password 的表单字段，表单 action 设置为当前页面 Path
        # AuthenticationForm 方法 clean 调用了 django.contrib.auth.authenticate

        # if (
        #         REDIRECT_FIELD_NAME not in request.GET
        #         and REDIRECT_FIELD_NAME not in request.POST
        # ):
        #     context[REDIRECT_FIELD_NAME] = index_path
        context.update(extra_context or {})

        defaults = {
            "extra_context": context,
            "authentication_form": self.login_page_form or AdminzAuthenticationForm,
            "template_name": self.login_page_template,
            "next_page": index_path
        }

        request.current_app = self.name

        class AdminzLoginView(LoginView):
            def get_success_url(self):
                return index_path

        return AdminzLoginView.as_view(**defaults)(request)

        # 这里可以添加登录逻辑，如检查用户名和密码
        if request.method == "GET":
            # 如果是GET请求，可以显示登录表单
            return TemplateResponse(
                request, self.login_page_template, context
            )

        else:
            # 如果是POST请求，处理登录表单提交
            username = request.POST.get('username')
            password = request.POST.get('password')
            if self.authenticate(username, password):
                # 如果认证成功，设置用户登录状态等操作
                pass
            else:
                # 如果认证失败，返回错误信息
                pass

    def logout(self, request, extra_context=None):

        index_path = reverse("{0}:index".format(self.name))
        login_path = reverse("{0}:login".format(self.name))
        logout_path = reverse("{0}:logout".format(self.name))

        # 清除用户登录状态相关的操作
        from django.contrib.auth.views import LogoutView

        defaults = {
            "extra_context": {
                **self.each_context(request),
                # Since the user isn't logged out at this point, the value of
                # has_permission must be overridden.
                "has_permission": False,
                **(extra_context or {})
            },
            "template_name": self.index_page_template,
            "next_page": login_path
        }
        request.current_app = self.name
        return LogoutView.as_view(**defaults)(request)

    def password_change(self, request):
        # 清除用户登录状态相关的操作
        pass

    def password_change_done(self, request):
        # 清除用户登录状态相关的操作
        pass

    def has_permission(self, request):
        """
        Return True if the given HttpRequest has permission to view
        *at least one* page in the admin site.
        """
        return request.user.is_active and request.user.is_staff

    def adminz_view(self, view, cacheable=False):
        """
        Decorator to create an admin view attached to this ``AdminSite``. This
        wraps the view and provides permission checking by calling
        ``self.has_permission``.

        You'll want to use this from within ``AdminSite.get_urls()``:

            class MyAdminSite(AdminSite):

                def get_urls(self):
                    from django.urls import path

                    urls = super().get_urls()
                    urls += [
                        path('my_view/', self.adminz_view(some_view))
                    ]
                    return urls

        By default, adminz_views are marked non-cacheable using the
        ``never_cache`` decorator. If the view can be safely cached, set
        cacheable=True.
        """

        def inner(request, *args, **kwargs):

            if not self.has_permission(request):
                # 没有权限登出跳转到/login?next=/logout会很奇怪，所以没有权限访问/logout时跳转/index
                if request.path == reverse("{0}:logout".format(self.name), current_app=self.name):
                    index_path = reverse("{0}:index".format(self.name), current_app=self.name)
                    return HttpResponseRedirect(index_path)
                # Inner import to prevent django.contrib.admin (app) from
                # importing django.contrib.auth.models.User (unrelated model).
                from django.contrib.auth.views import redirect_to_login

                return redirect_to_login(
                    request.get_full_path(),
                    reverse("{0}:login".format(self.name), current_app=self.name),
                )
            return view(request, *args, **kwargs)

        return update_wrapper(inner, view)

    @property
    def urls(self):
        # 路径 应用名 命名空间
        return self.get_urls(), "adminz", self.name

    def get_urls(self):

        # Since this module gets imported in the application's root package,
        # it cannot import models from other applications at the module level,
        # and django.contrib.contenttypes.views imports ContentType.
        from django.contrib.contenttypes import views as contenttype_views
        from django.urls import include, path, re_path

        def wrap(view, cacheable=False):
            def wrapper(*args, **kwargs):
                # Permission checking.
                return self.adminz_view(view, cacheable)(*args, **kwargs)
                # return view(*args, **kwargs)

            wrapper.admin_site = self
            # Used by LoginRequiredMiddleware.
            wrapper.login_url = reverse_lazy("{0}:login".format(self.name), current_app=self.name)
            return update_wrapper(wrapper, view)

        urlpatterns = [
            path('', wrap(self.index), name='index'),
            # login页面如果被权限检查装饰器装饰会陷入无限循环，所以无权限检查装饰器装饰
            # 在login view内部判断，若已登陆，就跳转其他页面
            path('login/', self.login, name='login'),
            path('logout/', wrap(self.logout), name='logout'),
            path("password_change/", wrap(self.password_change, cacheable=True), name="password_change"),
            path("password_change/done/", wrap(self.password_change_done, cacheable=True), name="password_change_done"),
        ]

        # 这里还需要添加与模型管理相关的URL模式（如模型列表视图、编辑视图等）
        for model, model_admin in self._models.items():
            app_label = model._meta.app_label
            model_name = model._meta.model_name
            print("app_label: {0}".format(app_label))
            print("model_name: {0}".format(model_name))
            urlpatterns.append(path("model/{0}/{1}".format(app_label, model_name), model_admin.urls))
            print(urlpatterns)

        for action, info in self._actions.items():
            action_name = info["action_name"]
            urlpatterns.append(
                path(
                    "action/{0}".format(action_name),
                    wrap(action),
                    name="action_{0}".format(action_name)
                )
            )

        return urlpatterns


site = AdminzSite()
